package com.alibaba.nacos.config.server.jpa.service.impl;

import java.io.IOException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;

import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog;
import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.config.server.enums.FileTypeEnum;
import com.alibaba.nacos.config.server.jpa.entity.AppConfigdataRelationSubsEntity;
import com.alibaba.nacos.config.server.jpa.entity.ConfigInfoAggrEntity;
import com.alibaba.nacos.config.server.jpa.entity.ConfigInfoBetaEntity;
import com.alibaba.nacos.config.server.jpa.entity.ConfigInfoEntity;
import com.alibaba.nacos.config.server.jpa.entity.ConfigInfoTagEntity;
import com.alibaba.nacos.config.server.jpa.entity.ConfigTagsRelationEntity;
import com.alibaba.nacos.config.server.jpa.entity.HisConfigInfoEntity;
import com.alibaba.nacos.config.server.jpa.entity.TenantInfoEntity;
import com.alibaba.nacos.config.server.jpa.repository.AppConfigdataRelationSubsRepository;
import com.alibaba.nacos.config.server.jpa.repository.ConfigInfoAggrRepository;
import com.alibaba.nacos.config.server.jpa.repository.ConfigInfoBetaRepository;
import com.alibaba.nacos.config.server.jpa.repository.ConfigInfoRepository;
import com.alibaba.nacos.config.server.jpa.repository.ConfigInfoTagRepository;
import com.alibaba.nacos.config.server.jpa.repository.ConfigTagsRelationRepository;
import com.alibaba.nacos.config.server.jpa.repository.HisConfigInfoRepository;
import com.alibaba.nacos.config.server.jpa.repository.TenantInfoRepository;
import com.alibaba.nacos.config.server.jpa.service.IPersistService;
import com.alibaba.nacos.config.server.jpa.utils.DateUtils;
import com.alibaba.nacos.config.server.model.ConfigAdvanceInfo;
import com.alibaba.nacos.config.server.model.ConfigAllInfo;
import com.alibaba.nacos.config.server.model.ConfigHistoryInfo;
import com.alibaba.nacos.config.server.model.ConfigInfo;
import com.alibaba.nacos.config.server.model.ConfigInfo4Beta;
import com.alibaba.nacos.config.server.model.ConfigInfo4Tag;
import com.alibaba.nacos.config.server.model.ConfigInfoAggr;
import com.alibaba.nacos.config.server.model.ConfigInfoBase;
import com.alibaba.nacos.config.server.model.ConfigInfoChanged;
import com.alibaba.nacos.config.server.model.ConfigInfoTagWrapper;
import com.alibaba.nacos.config.server.model.ConfigKey;
import com.alibaba.nacos.config.server.model.Page;
import com.alibaba.nacos.config.server.model.SameConfigPolicy;
import com.alibaba.nacos.config.server.model.SubInfo;
import com.alibaba.nacos.config.server.model.TenantInfo;
import com.alibaba.nacos.config.server.service.ConfigDataChangeEvent;
import com.alibaba.nacos.config.server.service.PersistService;
import com.alibaba.nacos.config.server.utils.MD5;
import com.alibaba.nacos.config.server.utils.ParamUtils;
import com.alibaba.nacos.config.server.utils.event.EventDispatcher;
import com.google.common.base.Joiner;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import sun.rmi.log.LogHandler;

/**
 * The type of nacos-all.
 *
 * @author 问道于盲
 * @date 2020-03-08
 */
@Service
public class PersisitServiceHandlerImpl implements IPersistService {

    @Autowired
    private ConfigInfoAggrRepository configInfoAggrRepository;

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public boolean addAggrConfigInfo(String dataId, String group, String tenant, String datumId, String appName,
                                     String content) {

        String appNameTmp = StringUtils.isBlank(appName) ? "" : appName;
        String tenantTmp = StringUtils.isBlank(tenant) ? "" : tenant;


        ConfigInfoAggrEntity entity = configInfoAggrRepository
            .queryByDataIdAndGroupIdAndTenantIdAndDatumId(
                dataId, group, tenantTmp, datumId);
        try {

            if (entity != null && Objects.equals(entity.getContent(), content)) {
                return true;
            } else {
                ConfigInfoAggrEntity data = build(dataId, group, tenantTmp, datumId, appNameTmp, content);
                BeanUtils.copyProperties(data, entity);
                configInfoAggrRepository.save(entity);
                return true;
            }
        } catch (EmptyResultDataAccessException ex) { // no data, insert
            configInfoAggrRepository.save(build(dataId, group, tenantTmp, datumId, appNameTmp, content));
            return true;
        }
    }

    private ConfigInfoAggrEntity build(String dataId, String group,
                                       String tenantTmp, String datumId,
                                       String appNameTmp, String content) {
        return new ConfigInfoAggrEntity()
            .setDataId(dataId)
            .setGroupId(group)
            .setTenantId(tenantTmp)
            .setContent(content)
            .setDatumId(datumId)
            .setAppName(appNameTmp)
            .setGmtModified(LocalDateTime.now());
    }

    @Autowired
    private ConfigInfoRepository configInfoRepository;

    /**
     * 增加配置；数据库原子操作，最小sql动作，无业务封装
     *
     * @param srcIp             ip
     * @param srcUser           user
     * @param configInfo        info
     * @param time              time
     * @param configAdvanceInfo advance info
     * @return excute sql result
     */
    private long addConfigInfoAtomic(final String srcIp, final String srcUser, final ConfigInfo configInfo,
                                     final Timestamp time,
                                     Map<String, Object> configAdvanceInfo) {
        final String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? ""
            : configInfo.getAppName();
        final String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? ""
            : configInfo.getTenant();

        final String desc = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("desc","");
        final String use = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("use","");
        final String effect = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("effect","");
        final String type = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("type","");
        final String schema = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("schema","");

        final String md5Tmp = MD5.getInstance().getMD5String(configInfo.getContent());

        ConfigInfoEntity data = new ConfigInfoEntity()
            .setDataId(configInfo.getDataId())
            .setGroupId(configInfo.getGroup())
            .setTenantId(tenantTmp)
            .setAppName(appNameTmp)
            .setContent(configInfo.getContent())
            .setMd5(md5Tmp)
            .setSrcIp(srcIp)
            .setSrcUser(srcUser)
            .setGmtCreate(DateUtils.convert(time))
            .setGmtModified(DateUtils.convert(time))
            .setCDesc(desc)
            .setCUse(use)
            .setEffect(effect)
            .setType(type)
            .setCSchema(schema);

        configInfoRepository.save(data);
        return data.getId();

    }

    @Autowired
    private HisConfigInfoRepository hisConfigInfoRepository;

    /**
     * 更新变更记录；数据库原子操作，最小sql动作，无业务封装
     *
     * @param id         id
     * @param configInfo config info
     * @param srcIp      ip
     * @param srcUser    user
     * @param time       time
     * @param ops        ops type
     */
    private void insertConfigHistoryAtomic(long id, ConfigInfo configInfo, String srcIp, String srcUser,
                                           final Timestamp time, String ops) {
        String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? "" : configInfo.getAppName();
        String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? "" : configInfo.getTenant();
        final String md5Tmp = MD5.getInstance().getMD5String(configInfo.getContent());


        HisConfigInfoEntity data = new HisConfigInfoEntity()
            .setDataId(configInfo.getDataId())
            .setGroupId(configInfo.getGroup()).setTenantId(tenantTmp)
            .setAppName(appNameTmp).setContent(configInfo.getContent())
            .setMd5(md5Tmp).setSrcIp(srcIp).setSrcUser(srcUser)
            .setGmtModified(DateUtils.convert(time))
            .setGmtCreate(LocalDateTime.now())
            .setOpType(ops);
        data.setId(id);

        hisConfigInfoRepository.save(data);

    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void addConfigInfo(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time, Map<String,
        Object> configAdvanceInfo, boolean notify) {

        long configId = addConfigInfoAtomic(srcIp, srcUser, configInfo, time, configAdvanceInfo);
        String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");
        addConfiTagsRelationAtomic(configId, configTags, configInfo.getDataId(), configInfo.getGroup(),
            configInfo.getTenant());
        insertConfigHistoryAtomic(0, configInfo, srcIp, srcUser, time, "I");
        if (notify) {
            EventDispatcher.fireEvent(
                new ConfigDataChangeEvent(false, configInfo.getDataId(), configInfo.getGroup(),
                    configInfo.getTenant(), time.getTime()));
        }
    }

    @Autowired
    private ConfigInfoBetaRepository configInfoBetaRepository;

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void addConfigInfo4Beta(ConfigInfo configInfo, String betaIps, String srcIp, String srcUser,
                                   Timestamp time, boolean notify) {
        String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName();
        String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant();
        String md5 = MD5.getInstance().getMD5String(configInfo.getContent());
        ConfigInfoBetaEntity data = new ConfigInfoBetaEntity()
            .setDataId(configInfo.getDataId())
            .setGroupId(configInfo.getGroup())
            .setTenantId(tenantTmp)
            .setAppName(appNameTmp)
            .setContent(configInfo.getContent())
            .setMd5(md5)
            .setBetaIps(betaIps)
            .setSrcIp(srcIp)
            .setSrcUser(srcUser)
            .setGmtCreate(DateUtils.convert(time))
            .setGmtModified(DateUtils.convert(time));

        configInfoBetaRepository.save(data);

        if (notify) {
            EventDispatcher.fireEvent(new ConfigDataChangeEvent(true, configInfo.getDataId(), configInfo.getGroup(),
                tenantTmp, time.getTime()));
        }
    }


    @Autowired
    private ConfigInfoTagRepository configInfoTagRepository;

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void addConfigInfo4Tag(ConfigInfo configInfo, String tag, String srcIp, String srcUser, Timestamp time,
                                  boolean notify) {
        String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ?
            StringUtils.EMPTY : configInfo.getAppName();
        String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ?
            StringUtils.EMPTY : configInfo.getTenant();
        String tagTmp = StringUtils.isBlank(tag) ? StringUtils.EMPTY
            : tag.trim();
        String md5 = MD5.getInstance().getMD5String(configInfo.getContent());

        ConfigInfoTagEntity data = new ConfigInfoTagEntity()
            .setDataId(configInfo.getDataId())
            .setGroupId(configInfo.getGroup())
            .setTenantId(tenantTmp)
            .setTagId(tagTmp)
            .setAppName(appNameTmp)
            .setContent(configInfo.getContent())
            .setMd5(md5)
            .setSrcIp(srcIp)
            .setSrcUser(srcUser)
            .setGmtCreate(DateUtils.convert(time))
            .setGmtModified(DateUtils.convert(time));
        configInfoTagRepository.save(data);
        if (notify) {
            EventDispatcher.fireEvent(new ConfigDataChangeEvent(false, configInfo.getDataId(),
                configInfo.getGroup(), tenantTmp, tagTmp, time.getTime()));
        }
    }

    @Autowired
    private ConfigTagsRelationRepository configTagsRelationRepository;

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void addConfiTagRelationAtomic(long configId, String tagName, String dataId, String group, String tenant) {
        ConfigTagsRelationEntity data = new ConfigTagsRelationEntity()
            .setTagName(tagName)
            .setDataId(dataId)
            .setGroupId(group)
            .setTenantId(tenant);
        data.setId(configId);
        configTagsRelationRepository.save(data);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void addConfiTagsRelationAtomic(long configId, String configTags, String dataId, String group,
                                           String tenant) {
        if (StringUtils.isNotBlank(configTags)) {
            String[] tagArr = configTags.split(",");
            for (String tag : tagArr) {
                addConfiTagRelationAtomic(configId, tag, dataId, group, tenant);
            }
        }

    }

    @Override

    public int aggrConfigInfoCount(String dataId, String group, String tenant) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        return configInfoAggrRepository.countByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);
    }

    @Override
    public int aggrConfigInfoCountIn(String dataId, String group, String tenant, List<String> datumIds) {
        return aggrConfigInfoCount(dataId, group, tenant, datumIds, true);
    }

    private int aggrConfigInfoCount(String dataId, String group, String tenant, List<String> datumIds,
                                    boolean isIn) {
        if (datumIds == null || datumIds.isEmpty()) {
            return 0;
        }
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        if (isIn) {
            return configInfoAggrRepository.countByDataIdAndGroupIdAndTenantIdAndDatumIdIn(dataId, group, tenantTmp,
                datumIds);

        }
        return configInfoAggrRepository.countByDataIdAndGroupIdAndTenantIdAndDatumIdNotIn(dataId, group, tenantTmp,
            datumIds);
    }

    @Override
    public int aggrConfigInfoCountNotIn(String dataId, String group,
                                        String tenant, List<String> datumIds) {
        return aggrConfigInfoCount(dataId, group, tenant, datumIds, false);
    }

    /**
     * @author klw
     * @Description: constant variables
     */
    public static final String SPOT = ".";

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public Map<String, Object> batchInsertOrUpdate(List<ConfigAllInfo> configInfoList, String srcUser, String srcIp,
                                                   Map<String, Object> configAdvanceInfo, Timestamp time,
                                                   boolean notify, SameConfigPolicy policy) throws NacosException {
        int succCount = 0;
        int skipCount = 0;
        List<Map<String, String>> failData = null;
        List<Map<String, String>> skipData = null;

        for (int i = 0; i < configInfoList.size(); i++) {
            ConfigAllInfo configInfo = configInfoList.get(i);
            try {
                ParamUtils.checkParam(configInfo.getDataId(), configInfo.getGroup(), "datumId",
                    configInfo.getContent());
            } catch (NacosException e) {
                defaultLog.error("data verification failed", e);
                throw e;
            }
            ConfigInfo configInfo2Save = new ConfigInfo(configInfo.getDataId(), configInfo.getGroup(),
                configInfo.getTenant(), configInfo.getAppName(), configInfo.getContent());

            String type = configInfo.getType();
            if (StringUtils.isBlank(type)) {
                // simple judgment of file type based on suffix
                if (configInfo.getDataId().contains(SPOT)) {
                    String extName =
                        configInfo.getDataId().substring(configInfo.getDataId().lastIndexOf(SPOT) + 1).toUpperCase();
                    try {
                        type = FileTypeEnum.valueOf(extName.toUpperCase()).getFileType();
                    } catch (Exception ex) {
                        type = FileTypeEnum.TEXT.getFileType();
                    }
                }
            }
            if (configAdvanceInfo == null) {
                configAdvanceInfo = new HashMap<>(16);
            }
            configAdvanceInfo.put("type", type);
            try {
                addConfigInfo(srcIp, srcUser, configInfo2Save, time, configAdvanceInfo, notify);
                succCount++;
            } catch (DataIntegrityViolationException ive) {
                // uniqueness constraint conflict
                if (SameConfigPolicy.ABORT.equals(policy)) {
                    failData = new ArrayList<>();
                    skipData = new ArrayList<>();
                    Map<String, String> faileditem = new HashMap<>(2);
                    faileditem.put("dataId", configInfo2Save.getDataId());
                    faileditem.put("group", configInfo2Save.getGroup());
                    failData.add(faileditem);
                    for (int j = (i + 1); j < configInfoList.size(); j++) {
                        ConfigInfo skipConfigInfo = configInfoList.get(j);
                        Map<String, String> skipitem = new HashMap<>(2);
                        skipitem.put("dataId", skipConfigInfo.getDataId());
                        skipitem.put("group", skipConfigInfo.getGroup());
                        skipData.add(skipitem);
                    }
                    break;
                } else if (SameConfigPolicy.SKIP.equals(policy)) {
                    skipCount++;
                    if (skipData == null) {
                        skipData = new ArrayList<>();
                    }
                    Map<String, String> skipitem = new HashMap<>(2);
                    skipitem.put("dataId", configInfo2Save.getDataId());
                    skipitem.put("group", configInfo2Save.getGroup());
                    skipData.add(skipitem);
                } else if (SameConfigPolicy.OVERWRITE.equals(policy)) {
                    succCount++;
                    updateConfigInfo(configInfo2Save, srcIp, srcUser, time, configAdvanceInfo, notify);
                }
            }
        }
        Map<String, Object> result = new HashMap<>(4);
        result.put("succCount", succCount);
        result.put("skipCount", skipCount);
        if (failData != null && !failData.isEmpty()) {
            result.put("failData", failData);
        }
        if (skipData != null && !skipData.isEmpty()) {
            result.put("skipData", skipData);
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public boolean batchPublishAggr(String dataId, String group, String tenant, Map<String, String> datumMap,
                                    String appName) {
        for (Map.Entry<String, String> entry : datumMap.entrySet()) {
            try {
                if (!addAggrConfigInfo(dataId, group, tenant, entry.getKey(), appName, entry.getValue())) {
                    throw new TransactionSystemException(
                        "error in addAggrConfigInfo");
                }
            } catch (Throwable e) {
                throw new TransactionSystemException(
                    "error in addAggrConfigInfo");
            }
        }
        return Boolean.TRUE;
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public boolean batchRemoveAggr(String dataId, String group, String tenant, List<String> datumList) {
        final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        configInfoAggrRepository.deleteByDataIdAndGroupIdAndTenantIdAndDatumIdIn(dataId, group, tenantTmp, datumList);
        return true;
    }

    @Override
    public int configInfoBetaCount() {
        return (int) configInfoBetaRepository.count();
    }

    @Override
    public int configInfoCount() {
        return (int) configInfoRepository.count();
    }

    @Override
    public int configInfoCount(String tenant) {
        return configInfoRepository.countByTenantIdLike(tenant);
    }

    @Override
    public int configInfoTagCount() {
        return (int) configInfoTagRepository.count();
    }

    @Override
    public ConfigHistoryInfo detailConfigHistory(Long nid) {
        HisConfigInfoEntity data = hisConfigInfoRepository.findByNid(nid);

        if (Objects.isNull(data)) {
            return null;
        }
        ConfigHistoryInfo historyInfo = new ConfigHistoryInfo();
        historyInfo.setId(data.getNid());
        historyInfo.setDataId(data.getDataId());
        historyInfo.setGroup(data.getGroupId());
        historyInfo.setTenant(data.getTenantId());
        historyInfo.setAppName(data.getAppName());
        historyInfo.setContent(data.getContent());
        historyInfo.setMd5(data.getMd5());
        historyInfo.setSrcUser(data.getSrcUser());
        historyInfo.setSrcIp(data.getSrcIp());
        historyInfo.setOpType(data.getOpType());
        historyInfo.setCreatedTime(DateUtils.convert(data.getGmtCreate()));
        historyInfo.setLastModifiedTime(DateUtils.convert(data.getGmtModified()));

        return historyInfo;
    }

    @Override
    public List<ConfigInfoChanged> findAllAggrGroup() {
        List<ConfigInfoAggrEntity> dataList = configInfoAggrRepository.findAll();
        dataList = dataList.stream().distinct().collect(Collectors.toList());
        List<ConfigInfoChanged> resultList = new ArrayList<>(dataList.size());
        for (ConfigInfoAggrEntity entity : dataList) {
            ConfigInfoChanged data = new ConfigInfoChanged();
            data.setTenant(entity.getTenantId());
            data.setGroup(entity.getGroupId());
            data.setDataId(entity.getDataId());
            resultList.add(data);
        }

        return resultList;
    }

    @Override
    public Page<ConfigInfo> findAllConfigInfo(int pageNo, int pageSize, String tenant) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        PageRequest pageRequest = PageRequest.of((pageNo - 1), pageSize);
        Specification<ConfigInfoEntity> specification =
            (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(root.get("tenantId"), tenantTmp);
        org.springframework.data.domain.Page<ConfigInfoEntity> entities
            = configInfoRepository.findAll(specification, pageRequest);

        Page<ConfigInfo> page = new Page<>();
        List<ConfigInfo> dataList = new ArrayList<>(entities.getNumberOfElements());
        page.setPageNumber(pageNo);
        page.setTotalCount((int) entities.getTotalElements());
        page.setPagesAvailable(entities.getTotalPages());
        for (ConfigInfoEntity entity : entities) {
            ConfigInfo configInfo = new ConfigInfo();
            configInfo.setDataId(entity.getDataId());
            configInfo.setGroup(entity.getGroupId());
            configInfo.setTenant(entity.getTenantId());
            configInfo.setAppName(entity.getAppName());
            configInfo.setContent(entity.getContent());
            configInfo.setMd5(entity.getMd5());
            configInfo.setId(entity.getId());
            configInfo.setType(entity.getType());

            dataList.add(configInfo);
        }
        page.setPageItems(dataList);
        return page;
    }

    private static String PATTERN_STR = "*";

    private String generateLikeArgument(String s) {
        String fuzzySearchSign = "\\*";
        String sqlLikePercentSign = "%";
        if (s.contains(PATTERN_STR)) {
            return s.replaceAll(fuzzySearchSign, sqlLikePercentSign);
        } else {
            return s;
        }
    }


    @Override
    public List<ConfigAllInfo> findAllConfigInfo4Export(String dataId, String group, String tenant, String appName,
                                                        List<Long> ids) {

        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        List<ConfigInfoEntity> dataList;
        if (!CollectionUtils.isEmpty(ids)) {
            dataList = configInfoRepository.findAllById(ids);
        } else {
            dataList = configInfoRepository
                .findByTenantIdAndGroupIdAndAppNameAndDataIdLike(tenantTmp, group, appName, dataId);
        }

        List<ConfigAllInfo> resultList = new ArrayList<>(dataList.size());
        for (ConfigInfoEntity data : dataList) {
            ConfigAllInfo configAllInfo = new ConfigAllInfo();
            configAllInfo.setDataId(data.getDataId());
            configAllInfo.setGroup(data.getGroupId());
            configAllInfo.setTenant(data.getTenantId());
            configAllInfo.setAppName(data.getAppName());
            configAllInfo.setContent(data.getContent());
            configAllInfo.setMd5(data.getMd5());
            configAllInfo.setId(data.getId());
            configAllInfo.setCreateTime(DateUtils.convertMilli(data.getGmtCreate()));
            configAllInfo.setModifyTime(DateUtils.convertMilli(data.getGmtCreate()));
            configAllInfo.setCreateUser(data.getSrcUser());
            configAllInfo.setCreateIp(data.getSrcIp());
            configAllInfo.setDesc(data.getCDesc());
            configAllInfo.setUse(data.getCUse());
            configAllInfo.setEffect(data.getEffect());
            configAllInfo.setType(data.getType());
            configAllInfo.setSchema(data.getCSchema());
            resultList.add(configAllInfo);
        }

        return resultList;

    }

    @Override
    public Page<ConfigInfoBase> findAllConfigInfoBase(int pageNo, int pageSize) {

        org.springframework.data.domain.Page<ConfigInfoEntity> datas =
            configInfoRepository.findAll(PageRequest.of(pageNo - 1, pageSize));

        List<ConfigInfoBase> dataList = new ArrayList<>();

        for (ConfigInfoEntity data : datas) {
            ConfigInfoBase info = new ConfigInfoBase();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setContent(data.getContent());
            info.setId(data.getId());
            dataList.add(info);
        }

        Page<ConfigInfoBase> page = new Page<>();
        page.setTotalCount((int) datas.getTotalElements());
        page.setPageNumber(pageNo);
        page.setPagesAvailable(datas.getTotalPages());
        page.setPageItems(dataList);
        return page;

    }

    @Override
    public Page<PersistService.ConfigInfoBetaWrapper> findAllConfigInfoBetaForDumpAll(int pageNo, int pageSize) {
        Sort sort = Sort.by("id");
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize, sort);
        org.springframework.data.domain.Page<ConfigInfoBetaEntity> datas
            = configInfoBetaRepository.findAll(pageRequest);
        List<PersistService.ConfigInfoBetaWrapper> dataList = new ArrayList<>();

        for (ConfigInfoBetaEntity data : datas) {
            PersistService.ConfigInfoBetaWrapper info = new PersistService.ConfigInfoBetaWrapper();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setContent(data.getContent());
            info.setId(data.getId());

            info.setTenant(data.getTenantId());
            info.setAppName(data.getAppName());
            info.setBetaIps(data.getBetaIps());
            info.setLastModified(DateUtils.convertMilli(data.getGmtModified()));
            info.setMd5(data.getMd5());

            dataList.add(info);
        }

        Page<PersistService.ConfigInfoBetaWrapper> page = new Page<>();
        page.setTotalCount((int) datas.getTotalElements());
        page.setPageNumber(pageNo);
        page.setPagesAvailable(datas.getTotalPages());
        page.setPageItems(dataList);
        return page;

    }

    @Override
    public Page<PersistService.ConfigInfoWrapper> findAllConfigInfoForDumpAll(int pageNo, int pageSize) {

        Sort sort = Sort.by("id");
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize, sort);
        org.springframework.data.domain.Page<ConfigInfoEntity> datas
            = configInfoRepository.findAll(pageRequest);
        List<PersistService.ConfigInfoWrapper> dataList = new ArrayList<>();

        for (ConfigInfoEntity data : datas) {
            PersistService.ConfigInfoWrapper info = new PersistService.ConfigInfoWrapper();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setContent(data.getContent());
            info.setId(data.getId());

            info.setTenant(data.getTenantId());
            info.setAppName(data.getAppName());
            info.setLastModified(DateUtils.convertMilli(data.getGmtModified()));
            info.setMd5(data.getMd5());
            info.setType(data.getType());
            dataList.add(info);
        }

        Page<PersistService.ConfigInfoWrapper> page = new Page<>();
        page.setTotalCount((int) datas.getTotalElements());
        page.setPageNumber(pageNo);
        page.setPagesAvailable(datas.getTotalPages());
        page.setPageItems(dataList);
        return page;

    }

    @Override
    public Page<PersistService.ConfigInfoWrapper> findAllConfigInfoFragment(long lastMaxId, int pageSize) {
        Sort sort = Sort.by("id");
        PageRequest pageRequest = PageRequest.of(0, pageSize, sort);
        org.springframework.data.domain.Page<ConfigInfoEntity> datas
            = configInfoRepository.findAll(pageRequest);
        List<PersistService.ConfigInfoWrapper> dataList = new ArrayList<>();

        for (ConfigInfoEntity data : datas) {
            PersistService.ConfigInfoWrapper info = new PersistService.ConfigInfoWrapper();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setTenant(data.getTenantId());
            info.setAppName(data.getAppName());
            info.setType(data.getType());
            info.setContent(data.getContent());
            info.setId(data.getId());
            info.setLastModified(DateUtils.convertMilli(data.getGmtModified()));
            info.setMd5(data.getMd5());
            dataList.add(info);
        }

        Page<PersistService.ConfigInfoWrapper> page = new Page<>();
        page.setTotalCount((int) datas.getTotalElements());
        page.setPageNumber(1);
        page.setPagesAvailable(datas.getTotalPages());
        page.setPageItems(dataList);
        return page;
    }

    @Override
    public Page<ConfigInfoTagWrapper> findAllConfigInfoTagForDumpAll(int pageNo, int pageSize) {

        Sort sort = Sort.by("id");
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize, sort);
        org.springframework.data.domain.Page<ConfigInfoTagEntity> datas
            = configInfoTagRepository.findAll(pageRequest);

        List<ConfigInfoTagWrapper> dataList = new ArrayList<>();

        for (ConfigInfoTagEntity data : datas) {
            ConfigInfoTagWrapper info = new ConfigInfoTagWrapper();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setAppName(data.getAppName());
            info.setTenant(data.getTenantId());
            info.setTag(data.getTagId());
            info.setContent(data.getContent());
            info.setId(data.getId());
            info.setLastModified(DateUtils.convertMilli(data.getGmtCreate()));
            info.setMd5(data.getMd5());
            dataList.add(info);
        }

        Page<ConfigInfoTagWrapper> page = new Page<>();
        page.setTotalCount((int) datas.getTotalElements());
        page.setPageNumber(pageNo);
        page.setPagesAvailable(datas.getTotalPages());
        page.setPageItems(dataList);
        return page;
    }

    @Override
    public Page<ConfigKey> findAllConfigKey(int pageNo, int pageSize, String tenant) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        Sort sort = Sort.by("id");
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize, sort);

        Specification<ConfigInfoEntity> specification
            = (root, criteriaQuery, criteriaBuilder) ->
            criteriaBuilder.like(root.get("tenantId"), tenantTmp);

        org.springframework.data.domain.Page<ConfigInfoEntity> datas
            = configInfoRepository.findAll(specification, pageRequest);

        List<ConfigKey> dataList = new ArrayList<>();

        for (ConfigInfoEntity data : datas) {
            ConfigKey info = new ConfigKey();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setAppName(data.getAppName());
            dataList.add(info);
        }

        Page<ConfigKey> page = new Page<>();
        page.setTotalCount((int) datas.getTotalElements());
        page.setPageNumber(pageNo);
        page.setPagesAvailable(datas.getTotalPages());
        page.setPageItems(dataList);
        return page;
    }

    @Override
    public List<ConfigInfo> findAllDataIdAndGroup() {
        List<ConfigInfoEntity> datas = configInfoRepository.findAll();
        return datas.stream().distinct().map(data -> {
            ConfigInfo info = new ConfigInfo();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setAppName(data.getAppName());
            info.setTenant(data.getTenantId());
            info.setContent(data.getContent());
            info.setId(data.getId());
            info.setMd5(data.getMd5());
            info.setType(data.getType());
            return info;

        }).collect(Collectors.toList());
    }

    @Override
    public Page<PersistService.ConfigInfoWrapper> findChangeConfig(String dataId, String group, String tenant,
                                                                   String appName,
                                                                   Timestamp startTime, Timestamp endTime, int pageNo
        , int pageSize,
                                                                   long lastMaxId) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        Specification<ConfigInfoEntity> specification = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (!StringUtils.isBlank(dataId)) {
                String dataIdTmp = generateLikeArgument(dataId);
                Path<String> path = root.get("dataId");
                Predicate predicate = criteriaBuilder.like(path, dataIdTmp);
                predicates.add(predicate);
            }

            if (!StringUtils.isBlank(group)) {
                String groupTmp = generateLikeArgument(group);
                Path<String> path = root.get("groupId");
                Predicate predicate = criteriaBuilder.like(path, groupTmp);
                predicates.add(predicate);

            }
            if (!StringUtils.isBlank(tenantTmp)) {
                Path<String> path = root.get("tenantId");
                Predicate predicate = criteriaBuilder.equal(path, tenant);
                predicates.add(predicate);
            }

            if (!StringUtils.isBlank(appName)) {
                Path<String> path = root.get("appName");
                Predicate predicate = criteriaBuilder.equal(path, appName);
                predicates.add(predicate);
            }
            if (startTime != null) {
                Path<LocalDateTime> path = root.get("gmtModified");
                Predicate predicate = criteriaBuilder.greaterThanOrEqualTo(path, DateUtils.convert(startTime));
                predicates.add(predicate);
            }
            if (endTime != null) {
                Path<LocalDateTime> path = root.get("gmtModified");
                Predicate predicate = criteriaBuilder.lessThanOrEqualTo(path, DateUtils.convert(endTime));
                predicates.add(predicate);
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
        };

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

        org.springframework.data.domain.Page<ConfigInfoEntity> datas = configInfoRepository.findAll(specification,
            pageRequest);

        List<PersistService.ConfigInfoWrapper> dataList = convertChangeConfig(datas.getContent());
        Page<PersistService.ConfigInfoWrapper> page = new Page<>();
        page.setTotalCount((int) datas.getTotalElements());
        page.setPageNumber(pageNo);
        page.setPagesAvailable(datas.getTotalPages());
        page.setPageItems(dataList);
        return page;

    }

    private List<PersistService.ConfigInfoWrapper> convertChangeConfig(List<ConfigInfoEntity> dataList) {
        return dataList.stream().map(data -> {
            String dataId = data.getDataId();
            String group = data.getGroupId();
            String tenant = data.getTenantId();
            String content = data.getContent();
            String appName = data.getAppName();
            String type = data.getType();
            Long id = data.getId();
            String md5 = data.getMd5();
            long mTime = DateUtils.convertMilli(data.getGmtModified());
            PersistService.ConfigInfoWrapper config = new PersistService.ConfigInfoWrapper();
            config.setDataId(dataId);
            config.setGroup(group);
            config.setTenant(tenant);
            config.setContent(content);
            config.setLastModified(mTime);
            config.setAppName(appName);
            config.setId(id);
            config.setType(type);
            config.setMd5(md5);
            return config;
        }).collect(Collectors.toList());
    }

    @Override
    public List<PersistService.ConfigInfoWrapper> findChangeConfig(Timestamp startTime, Timestamp endTime) {
        List<ConfigInfoEntity> dataList =
            configInfoRepository.findByGmtModifiedBetween(DateUtils.convert(startTime), DateUtils.convert(endTime));
        return convertChangeConfig(dataList);
    }

    @Override
    public ConfigAdvanceInfo findConfigAdvanceInfo(String dataId, String group, String tenant) {
        final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        List<String> configTagList = this.selectTagByConfig(dataId, group, tenant);

        ConfigInfoEntity data =
            configInfoRepository.findByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);

        ConfigAdvanceInfo configAdvance = convertConfigAdvanceInfo(data);
        if (configTagList != null && !configTagList.isEmpty()) {
            StringBuilder configTagsTmp = new StringBuilder();
            for (String configTag : configTagList) {
                if (configTagsTmp.length() == 0) {
                    configTagsTmp.append(configTag);
                } else {
                    configTagsTmp.append(",").append(configTag);
                }
            }
            configAdvance.setConfigTags(configTagsTmp.toString());
        }
        return configAdvance;
    }

    private ConfigAdvanceInfo convertConfigAdvanceInfo(ConfigInfoEntity data) {
        ConfigAdvanceInfo configAdvance = new ConfigAdvanceInfo();
        configAdvance.setCreateTime(DateUtils.convertMilli(data.getGmtCreate()));
        configAdvance.setModifyTime(DateUtils.convertMilli(data.getGmtModified()));
        configAdvance.setCreateUser(data.getSrcUser());
        configAdvance.setCreateIp(data.getSrcIp());
        configAdvance.setDesc(data.getCDesc());
        configAdvance.setUse(data.getCUse());
        configAdvance.setEffect(data.getEffect());
        configAdvance.setType(data.getType());
        configAdvance.setSchema(data.getCSchema());
        return configAdvance;
    }

    @Override
    public ConfigAllInfo findConfigAllInfo(String dataId, String group, String tenant) {
        final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        List<String> configTagList = this.selectTagByConfig(dataId, group, tenant);

        ConfigInfoEntity data =
            configInfoRepository.findByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);

        if (data == null) {
            return null;
        }

        ConfigAllInfo configAdvance = new ConfigAllInfo();

        configAdvance.setCreateTime(DateUtils.convertMilli(data.getGmtCreate()));
        configAdvance.setModifyTime(DateUtils.convertMilli(data.getGmtModified()));
        configAdvance.setCreateUser(data.getSrcUser());
        configAdvance.setCreateIp(data.getSrcIp());
        configAdvance.setDesc(data.getCDesc());
        configAdvance.setUse(data.getCUse());
        configAdvance.setEffect(data.getEffect());
        configAdvance.setType(data.getType());
        configAdvance.setSchema(data.getCSchema());
        configAdvance.setDataId(data.getDataId());
        configAdvance.setGroup(data.getGroupId());
        configAdvance.setTenant(data.getTenantId());
        configAdvance.setAppName(data.getAppName());
        configAdvance.setContent(data.getContent());
        configAdvance.setMd5(data.getMd5());
        configAdvance.setId(data.getId());

        if (configTagList != null && !configTagList.isEmpty()) {
            StringBuilder configTagsTmp = new StringBuilder();
            for (String configTag : configTagList) {
                if (configTagsTmp.length() == 0) {
                    configTagsTmp.append(configTag);
                } else {
                    configTagsTmp.append(",").append(configTag);
                }
            }
            configAdvance.setConfigTags(configTagsTmp.toString());
        }
        return configAdvance;

    }

    @Override
    public Page<ConfigHistoryInfo> findConfigHistory(String dataId, String group, String tenant, int pageNo,
                                                     int pageSize) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        Sort sort = Sort.by(Sort.Order.desc("nid"));
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize, sort);

        Specification<HisConfigInfoEntity> specification = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(criteriaBuilder.equal(root.get("dataId"), dataId));
            predicates.add(criteriaBuilder.equal(root.get("groupId"), group));
            predicates.add(criteriaBuilder.equal(root.get("tenantId"), tenantTmp));
            return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
        };

        org.springframework.data.domain.Page<HisConfigInfoEntity> datas =
            hisConfigInfoRepository.findAll(specification, pageRequest);

        List<ConfigHistoryInfo> dataList = datas.stream().map(data -> {
            ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo();
            configHistoryInfo.setId(data.getId());
            configHistoryInfo.setDataId(data.getDataId());
            configHistoryInfo.setGroup(data.getGroupId());
            configHistoryInfo.setTenant(data.getTenantId());
            configHistoryInfo.setAppName(data.getAppName());
            configHistoryInfo.setSrcIp(data.getSrcIp());
            configHistoryInfo.setOpType(data.getOpType());
            configHistoryInfo.setCreatedTime(DateUtils.convert(data.getGmtCreate()));
            configHistoryInfo.setLastModifiedTime(DateUtils.convert(data.getGmtModified()));
            return configHistoryInfo;
        }).collect(Collectors.toList());

        Page<ConfigHistoryInfo> page = new Page<>();
        page.setTotalCount((int) datas.getTotalElements());
        page.setPageNumber(pageNo);
        page.setPagesAvailable(datas.getTotalPages());
        page.setPageItems(dataList);
        return page;
    }

    @Override
    public int findConfigHistoryCountByTime(Timestamp startTime) {
        return hisConfigInfoRepository.countByGmtModifiedLessThan(DateUtils.convert(startTime));
    }

    @Override
    public ConfigInfo findConfigInfo(long id) {
        Optional<ConfigInfoEntity> optional = configInfoRepository.findById(id);
        if (!optional.isPresent()) {
            return null;
        }
        ConfigInfoEntity data = optional.get();
        ConfigInfo info = new ConfigInfo();
        info.setDataId(data.getDataId());
        info.setGroup(data.getGroupId());
        info.setTenant(data.getTenantId());
        info.setAppName(data.getAppName());
        info.setContent(data.getContent());
        info.setMd5(data.getMd5());
        info.setId(data.getId());
        info.setType(data.getType());

        return info;
    }

    @Override
    public ConfigInfo findConfigInfo(String dataId, String group, String tenant) {
        final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        ConfigInfoEntity data =
            configInfoRepository.findByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);
        if (data == null) {
            return null;
        }

        ConfigInfo info = new ConfigInfo();
        info.setDataId(data.getDataId());
        info.setGroup(data.getGroupId());
        info.setTenant(data.getTenantId());
        info.setAppName(data.getAppName());
        info.setContent(data.getContent());
        info.setMd5(data.getMd5());
        info.setId(data.getId());
        info.setType(data.getType());
        return info;
    }

    @Override
    public ConfigInfo4Beta findConfigInfo4Beta(String dataId, String group, String tenant) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        ConfigInfoBetaEntity data =
            configInfoBetaRepository.findByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);

        if (data == null) {
            return null;
        }

        ConfigInfo4Beta info = new ConfigInfo4Beta();
        info.setDataId(data.getDataId());
        info.setGroup(data.getGroupId());
        info.setTenant(data.getTenantId());
        info.setAppName(data.getAppName());
        info.setBetaIps(data.getBetaIps());
        info.setContent(data.getContent());
        info.setId(data.getId());
        info.setMd5(data.getMd5());
        return info;

    }

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @Override
    public Page<ConfigInfo> findConfigInfo4Page(int pageNo, int pageSize, String dataId, String group, String tenant,
                                                Map<String, Object> configAdvanceInfo) {

        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName");
        final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);
        if (!StringUtils.isNotBlank(configTags)) {
            Specification<ConfigInfoEntity> specification = (root, criteriaQuery, criteriaBuilder) -> {
                List<Predicate> predicates = new ArrayList<>();
                predicates.add(criteriaBuilder.equal(root.get("tenantId"), tenantTmp));
                if (StringUtils.isNotBlank(dataId)) {
                    predicates.add(criteriaBuilder.equal(root.get("dataId"),
                        dataId));
                }
                if (StringUtils.isNotBlank(group)) {
                    predicates.add(criteriaBuilder.equal(root.get("groupId"),
                        group));
                }
                if (StringUtils.isNotBlank(appName)) {
                    predicates.add(criteriaBuilder.equal(root.get("appName"),
                        appName));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
            };
            org.springframework.data.domain.Page<ConfigInfoEntity> datas = configInfoRepository.findAll(specification,
                pageRequest);

            List<ConfigInfo> list = datas.stream().map(this::convertConfigInfo).collect(Collectors.toList());
            Page<ConfigInfo> page = new Page<>();
            page.setPagesAvailable(datas.getTotalPages());
            page.setPageNumber(pageNo);
            page.setTotalCount((int) datas.getTotalElements());
            page.setPageItems(list);
            return page;
        }

        String queryJpql = "select ci from ConfigInfoEntity ci left join ConfigTagsRelationEntity ctr on ci.id = ctr" +
            ".id";
        String countJpql = "select count(*) from ConfigInfoEntity ci left join ConfigTagsRelationEntity ctr on ci.id " +
            "= ctr.id";

        EntityManager entityManager = entityManagerFactory.createEntityManager();

        queryJpql += " ci.tenantId = :tenantId";
        countJpql += " ci.tenantId = :tenantId";

        if (StringUtils.isNotBlank(dataId)) {
            queryJpql += " and ci.dataId = :dataId";
            countJpql += " and ci.dataId = :dataId";
        }
        if (StringUtils.isNotBlank(group)) {
            queryJpql += " and ci.groupId = :groupId";
            countJpql += " and ci.groupId = :groupId";
        }
        if (StringUtils.isNotBlank(appName)) {
            queryJpql += " and ci.appName = :appName";
            countJpql += " and ci.appName = :appName";
        }

        queryJpql += " and ctr.tagName in ";
        countJpql += " and ctr.tagName in ";

        String[] tagArr = configTags.split(",");
        queryJpql += Arrays.asList(tagArr).toString()
            .replace(", ", "', '")
            .replace("[", "('")
            .replace("]", "')");
        countJpql += Arrays.asList(tagArr).toString()
            .replace(", ", "', '")
            .replace("[", "('")
            .replace("]", "')");

        TypedQuery<ConfigInfoEntity> query = entityManager.createQuery(queryJpql, ConfigInfoEntity.class);
        TypedQuery<Integer> count = entityManager.createQuery(countJpql, Integer.class);

        if (StringUtils.isNotBlank(dataId)) {
            query.setParameter("dataId", dataId);
            count.setParameter("dataId", dataId);
        }
        if (StringUtils.isNotBlank(group)) {
            query.setParameter("groupId", group);
            count.setParameter("groupId", group);
        }
        if (StringUtils.isNotBlank(appName)) {
            query.setParameter("appName", appName);
            count.setParameter("appName", appName);
        }

        Integer totalResult = count.getSingleResult();

        List<ConfigInfoEntity> datas =
            query.setFirstResult((pageNo - 1) * pageSize).
                setMaxResults(pageSize)
                .getResultList();

        List<ConfigInfo> list = datas.stream().map(this::convertConfigInfo).collect(Collectors.toList());
        Page<ConfigInfo> page = new Page<>();
        page.setPagesAvailable((totalResult - 1) / pageSize + 1);
        page.setPageNumber(pageNo);
        page.setTotalCount(totalResult);
        page.setPageItems(list);
        return page;
    }

    private ConfigInfo convertConfigInfo(ConfigInfoEntity data) {
        ConfigInfo info = new ConfigInfo();

        info.setDataId(data.getDataId());
        info.setGroup(data.getGroupId());
        info.setTenant(data.getTenantId());
        info.setAppName(data.getAppName());
        info.setContent(data.getContent());
        info.setMd5(data.getMd5());
        info.setId(data.getId());
        info.setType(data.getType());
        return info;
    }

    @Override
    public ConfigInfo4Tag findConfigInfo4Tag(String dataId, String group, String tenant, String tag) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        String tagTmp = StringUtils.isBlank(tag) ? StringUtils.EMPTY : tag.trim();
        ConfigInfoTagEntity data =
            configInfoTagRepository.findByDataIdAndGroupIdAndTenantIdAndTagId(dataId, group, tenantTmp, tagTmp);
        if (data == null) {
            return null;
        }
        ConfigInfo4Tag info = new ConfigInfo4Tag();
        info.setDataId(data.getDataId());
        info.setGroup(data.getGroupId());
        info.setTenant(data.getTenantId());
        info.setTag(data.getTagId());
        info.setAppName(data.getAppName());
        info.setContent(data.getContent());
        info.setId(data.getId());
        info.setMd5(data.getMd5());
        return info;
    }

    @Override
    public ConfigInfo findConfigInfoAdvanceInfo(String dataId, String group, String tenant,
                                                Map<String, Object> configAdvanceInfo) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName");
        final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");
        if (!StringUtils.isNotBlank(configTags)) {
            ConfigInfoEntity data;

            if (StringUtils.isBlank(appName)) {
                data = configInfoRepository
                    .findByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);
            } else {
                data = configInfoRepository
                    .findByDataIdAndGroupIdAndTenantIdAndAppName(dataId, group, tenantTmp, appName);
            }

            return convertConfigInfo(data);
        }
        ConfigInfoEntity data;
        String[] tagArr = configTags.split(",");

        if (StringUtils.isNotBlank(appName)) {
            data = configInfoRepository.findOne(
                dataId, group, tenantTmp, Arrays.asList(tagArr), appName);
        } else {
            data = configInfoRepository.findOne(
                dataId, group, tenantTmp, Arrays.asList(tagArr));
        }
        return convertConfigInfo(data);
    }

    @Override
    public List<ConfigInfoAggr> findConfigInfoAggr(String dataId, String group, String tenant) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        List<ConfigInfoAggrEntity> dataList = configInfoAggrRepository
            .findByDataIdAndGroupIdAndTenantIdOrderByDatumId(dataId, group, tenantTmp);
        return dataList.stream().map(data -> {
            ConfigInfoAggr info = new ConfigInfoAggr();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setDatumId(data.getDatumId());
            info.setTenant(data.getTenantId());
            info.setAppName(data.getAppName());
            info.setContent(data.getContent());
            return info;
        }).collect(Collectors.toList());
    }

    @Override
    public Page<ConfigInfoAggr> findConfigInfoAggrByPage(String dataId, String group, String tenant, int pageNo,
                                                         int pageSize) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize,
            Sort.by(Sort.Order.asc("datumId")));
        org.springframework.data.domain.Page<ConfigInfoAggrEntity> datas =
            configInfoAggrRepository.findByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp, pageRequest);
        List<ConfigInfoAggr> list = datas.stream().map(data -> {
            ConfigInfoAggr info = new ConfigInfoAggr();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setDatumId(data.getDatumId());
            info.setTenant(data.getTenantId());
            info.setAppName(data.getAppName());
            info.setContent(data.getContent());
            return info;
        }).collect(Collectors.toList());

        Page<ConfigInfoAggr> page = new Page<>();
        page.setPageItems(list);
        page.setTotalCount((int) datas.getTotalElements());
        page.setPageNumber(pageNo);
        page.setPagesAvailable(datas.getTotalPages());

        return page;
    }

    @Override
    public Page<ConfigInfoAggr> findConfigInfoAggrLike(int pageNo, int pageSize, ConfigKey[] configKeys,
                                                       boolean blacklist) {
        // 白名单，请同步条件为空，则没有符合条件的配置
        if (configKeys.length == 0 && !blacklist) {
            Page<ConfigInfoAggr> page = new Page<>();
            page.setTotalCount(0);
            return page;
        }

        String where = " 1=1 ";
        String jpql = "from ConfigInfoAggrEntity";

        boolean isFirst = true;
        for (ConfigKey configInfoAggr : configKeys) {
            String dataId = configInfoAggr.getDataId();
            String group = configInfoAggr.getGroup();
            String appName = configInfoAggr.getAppName();
            if (StringUtils.isBlank(dataId)
                && StringUtils.isBlank(group)
                && StringUtils.isBlank(appName)) {
                break;
            }
            if (blacklist) {
                if (isFirst) {
                    isFirst = false;
                    where += " and ";
                } else {
                    where += " and ";
                }

                where += "(";
                boolean isFirstSub = true;
                if (!StringUtils.isBlank(dataId)) {
                    where += " dataId not like '" + generateLikeArgument(dataId) + "'";
                    isFirstSub = false;
                }
                if (!StringUtils.isBlank(group)) {
                    if (!isFirstSub) {
                        where += " or ";
                    }
                    where += " groupId like '" + generateLikeArgument(group) + "'";
                    isFirstSub = false;
                }
                if (!StringUtils.isBlank(appName)) {
                    if (!isFirstSub) {
                        where += " or ";
                    }
                    where += " app_name != '" + appName + "'";
                    isFirstSub = false;
                }
                where += ") ";
            } else {
                if (isFirst) {
                    isFirst = false;
                    where += " and ";
                } else {
                    where += " or ";
                }
                where += "(";
                boolean isFirstSub = true;
                if (!StringUtils.isBlank(dataId)) {
                    where += " dataId not like '" + generateLikeArgument(dataId) + "'";
                    isFirstSub = false;
                }
                if (!StringUtils.isBlank(group)) {
                    if (!isFirstSub) {
                        where += " and ";
                    }
                    where += " groupId like '" + generateLikeArgument(group) + "'";
                    isFirstSub = false;
                }
                if (!StringUtils.isBlank(appName)) {
                    if (!isFirstSub) {
                        where += " and ";
                    }
                    where += " app_name = '" + appName + "'";
                    isFirstSub = false;
                }
                where += ") ";
            }
        }

        jpql = jpql + where;
//        CONFIG_INFO_AGGR_ROW_MAPPER

        EntityManager entityManager = entityManagerFactory.createEntityManager();
        TypedQuery<ConfigInfoAggrEntity> query = entityManager.createQuery(jpql, ConfigInfoAggrEntity.class);
        List<ConfigInfoAggrEntity> datas =
            query.setMaxResults(pageSize).setFirstResult((pageNo - 1) * pageSize).getResultList();
        TypedQuery<Integer> countQuery = entityManager.createQuery("select count(*) " + jpql, Integer.class);
        Integer count = countQuery.getSingleResult();

        List<ConfigInfoAggr> list = datas.stream().map(data -> {
            ConfigInfoAggr info = new ConfigInfoAggr();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setDatumId(data.getDatumId());
            info.setTenant(data.getTenantId());
            info.setAppName(data.getAppName());
            info.setContent(data.getContent());
            return info;
        }).collect(Collectors.toList());

        Page<ConfigInfoAggr> page = new Page<>();
        page.setPagesAvailable((count - 1) / pageSize + 1);
        page.setTotalCount(count);
        page.setPageNumber(pageNo);
        page.setPageItems(list);
        return page;

    }

    @Override
    public ConfigInfo findConfigInfoApp(String dataId, String group, String tenant, String appName) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        ConfigInfoEntity data =
            configInfoRepository.findByDataIdAndGroupIdAndTenantIdAndAppName(dataId, group, tenantTmp, appName);

        if (data == null) {
            return null;
        }

        return convertConfigInfo(data);
    }

    @Override
    public ConfigInfoBase findConfigInfoBase(String dataId, String group) {
        ConfigInfoEntity data =
            configInfoRepository.findByDataIdAndGroupIdAndTenantId(dataId, group, StringUtils.EMPTY);

        if (data == null) {
            return null;
        }
        ConfigInfoBase info = new ConfigInfoBase();

        info.setDataId(data.getDataId());
        info.setGroup(data.getGroupId());
        info.setContent(data.getContent());
        info.setId(data.getId());
        return info;
    }


    private <E> Page<E> convertPage(org.springframework.data.domain.Page<?> jpaPage,
                                    int pageNo, List<E> datas) {
        Page<E> page = new Page<>();
        page.setPagesAvailable(jpaPage.getTotalPages());
        page.setTotalCount((int) jpaPage.getTotalElements());
        page.setPageNumber(pageNo);
        page.setPageItems(datas);
        return page;
    }

    @Override
    public Page<ConfigInfoBase> findConfigInfoBaseByDataId(int pageNo, int pageSize, String dataId) {

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

        org.springframework.data.domain.Page<ConfigInfoEntity> datas =
            configInfoRepository.findByTenantIdAndDataId(dataId, StringUtils.EMPTY, pageRequest);
        List<ConfigInfoBase> list = datas.stream().map(data -> {
            ConfigInfoBase info = new ConfigInfoBase();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setContent(data.getContent());
            info.setId(data.getId());
            return info;
        }).collect(Collectors.toList());

        return convertPage(datas, pageNo, list);

    }

    @Override
    public Page<ConfigInfoBase> findConfigInfoBaseByGroup(int pageNo, int pageSize, String group) {

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

        org.springframework.data.domain.Page<ConfigInfoEntity> datas =
            configInfoRepository.findByTenantIdAndGroupId(group, StringUtils.EMPTY, pageRequest);
        List<ConfigInfoBase> list = datas.stream().map(data -> {
            ConfigInfoBase info = new ConfigInfoBase();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setContent(data.getContent());
            info.setId(data.getId());
            return info;
        }).collect(Collectors.toList());

        return convertPage(datas, pageNo, list);
    }

    @Override
    public Page<ConfigInfoBase> findConfigInfoBaseLike(int pageNo, int pageSize, String dataId, String group,
                                                       String content) throws IOException {
        if (StringUtils.isBlank(dataId) && StringUtils.isBlank(group)) {
            throw new IOException("invalid param");
        }
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

        Specification<ConfigInfoEntity> specification = (root, criteriaQuery, criteriaBuilder) -> {

            List<Predicate> predicates = new ArrayList<>();
            predicates.add(criteriaBuilder.equal(root.get("tenantId"), StringUtils.EMPTY));
            if (!StringUtils.isBlank(dataId)) {
                predicates.add(criteriaBuilder.like(root.get("dataId"), generateLikeArgument(dataId)));
            }
            if (!StringUtils.isBlank(group)) {
                predicates.add(criteriaBuilder.like(root.get("groupId"), generateLikeArgument(group)));
            }
            if (!StringUtils.isBlank(content)) {
                predicates.add(criteriaBuilder.like(root.get("content"), generateLikeArgument(content)));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
        };

        org.springframework.data.domain.Page<ConfigInfoEntity> datas =
            configInfoRepository.findAll(specification, pageRequest);

        List<ConfigInfoBase> list = datas.stream().map(data -> {
            ConfigInfoBase info = new ConfigInfoBase();
            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setContent(data.getContent());
            info.setId(data.getId());
            return info;
        }).collect(Collectors.toList());

        return convertPage(datas, pageNo, list);
    }

    @Override
    public Page<ConfigInfo> findConfigInfoByAdvance(int pageNo, int pageSize, String tenant,
                                                    Map<String, Object> configAdvanceInfo) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName");
        final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

        org.springframework.data.domain.Page<ConfigInfoEntity> datas;

        if (StringUtils.isBlank(configTags)) {
            if (StringUtils.isNotBlank(appName)) {
                datas = configInfoRepository.findByAppNameAndTenantIdLike(appName, tenantTmp, pageRequest);
            } else {
                datas = configInfoRepository.findByTenantIdLike(tenantTmp, pageRequest);
            }
        } else {
            String[] tagArr = configTags.split(",");

            if (StringUtils.isNotBlank(appName)) {
                datas = configInfoRepository.findByTenantIdAndAppNameAndTagNames(tenantTmp, appName,
                    Arrays.asList(tagArr), pageRequest);
            } else {
                datas = configInfoRepository.findByTenantIdAndTagNames(tenantTmp, Arrays.asList(tagArr), pageRequest);
            }
        }

        return convertConfigInfoList(pageNo, datas);


    }

    @Override
    public Page<ConfigInfo> findConfigInfoByApp(int pageNo, int pageSize, String tenant, String appName) {

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        org.springframework.data.domain.Page<ConfigInfoEntity> datas =
            configInfoRepository.findByAppNameAndTenantIdLike(appName, generateLikeArgument(tenantTmp), pageRequest);

        return convertConfigInfoList(pageNo, datas);
    }

    private final static int QUERY_LIMIT_SIZE = 50;

    @Override
    public List<ConfigInfo> findConfigInfoByBatch(List<String> dataIds, String group, String tenant,
                                                  int subQueryLimit) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        // if dataids empty return empty list
        if (CollectionUtils.isEmpty(dataIds)) {
            return Collections.emptyList();
        }

        // 批量查询上限
        // in 个数控制在100内, sql语句长度越短越好
        if (subQueryLimit > QUERY_LIMIT_SIZE) {
            subQueryLimit = 50;
        }
        List<ConfigInfo> result = new ArrayList<>(dataIds.size());

        List<ConfigInfoEntity> datas = new ArrayList<>();
        int queryCount = (dataIds.size() - 1) / subQueryLimit + 1;

        for (int i = 1; i <= queryCount; i++) {
            int end = i * subQueryLimit;
            if (end >= (dataIds.size() - 1)) {
                end = dataIds.size() - 1;
            }
            List<String> tempIds = dataIds.subList((i - 1) * subQueryLimit, end);
            List<ConfigInfoEntity> tempDatas =
                configInfoRepository.findAllByGroupIdAndTenantIdAndDataIdIn(group, tenantTmp, tempIds);
            datas.addAll(tempDatas);
        }

        return datas.stream().map(data -> {
            ConfigInfo info = new ConfigInfo();

            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setTenant(data.getTenantId());
            info.setAppName(data.getAppName());
            info.setContent(data.getContent());
            info.setMd5(data.getMd5());
            info.setId(data.getId());
            info.setType(data.getType());
            return info;
        }).collect(Collectors.toList());


    }

    @Override
    public Page<ConfigInfo> findConfigInfoByDataId(int pageNo, int pageSize, String dataId, String tenant) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

        org.springframework.data.domain.Page<ConfigInfoEntity> datas =
            configInfoRepository.findByTenantIdAndDataId(tenantTmp, dataId, pageRequest);

        return convertConfigInfoList(pageNo, datas);
    }

    @Override
    public Page<ConfigInfo> findConfigInfoByDataIdAndAdvance(int pageNo, int pageSize, String dataId, String tenant,
                                                             Map<String, Object> configAdvanceInfo) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName");
        final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);
        org.springframework.data.domain.Page<ConfigInfoEntity> datas;
        if (StringUtils.isBlank(configTags)) {

            if (StringUtils.isNotBlank(appName)) {
                datas = configInfoRepository.findByTenantIdAndDataIdAndAppName(tenantTmp, dataId, appName, pageRequest);

            } else {
                datas = configInfoRepository.findByTenantIdAndDataId(tenantTmp, dataId, pageRequest);
            }
        } else {
            String[] tagArr = configTags.split(",");
            if (StringUtils.isNotBlank(appName)) {
                datas = configInfoRepository.findByTenantIdAndDataIdAndAppNameAndTagNames(
                    tenantTmp, dataId, appName, Arrays.asList(tagArr), pageRequest);
            } else {
                datas = configInfoRepository.findByTenantIdAndDataIdAndTagNames(
                    tenantTmp, dataId, Arrays.asList(tagArr), pageRequest);

            }
        }

        return convertConfigInfoList(pageNo, datas);
    }

    @Override
    public Page<ConfigInfo> findConfigInfoByDataIdAndApp(int pageNo, int pageSize, String dataId, String tenant,
                                                         String appName) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);
        org.springframework.data.domain.Page<ConfigInfoEntity> datas
            = configInfoRepository.findByTenantIdAndDataIdAndAppName(tenantTmp, dataId, appName, pageRequest);

        return convertConfigInfoList(pageNo, datas);

    }

    @Override
    public Page<ConfigInfo> findConfigInfoByGroup(int pageNo, int pageSize, String group, String tenant) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);
        org.springframework.data.domain.Page<ConfigInfoEntity> datas
            = configInfoRepository.findByTenantIdAndGroupId(tenantTmp, group, pageRequest);

        return convertConfigInfoList(pageNo, datas);

    }

    private Page<ConfigInfo> convertConfigInfoList(int pageNo,
                                                   org.springframework.data.domain.Page<ConfigInfoEntity> datas) {
        List<ConfigInfo> list = datas.stream().map(data -> {
            ConfigInfo info = new ConfigInfo();

            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setTenant(data.getTenantId());
            info.setAppName(data.getAppName());
            info.setContent(data.getContent());
            info.setMd5(data.getMd5());
            info.setId(data.getId());
            info.setType(data.getType());
            return info;
        }).collect(Collectors.toList());

        return convertPage(datas, pageNo, list);
    }

    @Override
    public Page<ConfigInfo> findConfigInfoByGroupAndAdvance(int pageNo, int pageSize, String group, String tenant,
                                                            Map<String, Object> configAdvanceInfo) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName");
        final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

        org.springframework.data.domain.Page<ConfigInfoEntity> datas;

        if (StringUtils.isBlank(configTags)) {
            if (StringUtils.isNotBlank(appName)) {
                datas = configInfoRepository.findByTenantIdAndGroupIdAndAppName(tenantTmp, group, appName, pageRequest);
            } else {
                datas = configInfoRepository.findByTenantIdAndGroupId(tenantTmp, group, pageRequest);
            }
        } else {
            String[] tagArr = configTags.split(",");
            if (StringUtils.isNotBlank(appName)) {
                datas = configInfoRepository.findByTenantIdAndAppNameAndTagNames(
                    tenantTmp, appName, Arrays.asList(tagArr), pageRequest);
            } else {
                datas = configInfoRepository.findByTenantIdAndTagNames(
                    tenantTmp, Arrays.asList(tagArr), pageRequest);
            }
        }

        return convertConfigInfoList(pageNo, datas);

    }

    @Override
    public Page<ConfigInfo> findConfigInfoByGroupAndApp(int pageNo, int pageSize, String group, String tenant,
                                                        String appName) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);
        org.springframework.data.domain.Page<ConfigInfoEntity> datas =
            configInfoRepository.findByTenantIdAndGroupIdAndAppName(tenantTmp, group, appName, pageRequest);

        return convertConfigInfoList(pageNo, datas);
    }

    @Override
    public Page<ConfigInfo> findConfigInfoLike(int pageNo, int pageSize, ConfigKey[] configKeys, boolean blacklist) {

        // 白名单，请同步条件为空，则没有符合条件的配置
        if (configKeys.length == 0 && !blacklist) {
            Page<ConfigInfo> page = new Page<>();
            page.setTotalCount(0);
            return page;
        }

        String where = " 1=1 ";

        String jpql = "from ConfigInfoEntity where";

        boolean isFirst = true;
        for (ConfigKey configInfo : configKeys) {
            String dataId = configInfo.getDataId();
            String group = configInfo.getGroup();
            String appName = configInfo.getAppName();

            if (StringUtils.isBlank(dataId)
                && StringUtils.isBlank(group)
                && StringUtils.isBlank(appName)) {
                break;
            }

            if (blacklist) {
                if (isFirst) {
                    isFirst = false;
                    where += " and ";
                } else {
                    where += " and ";
                }

                where += "(";
                boolean isFirstSub = true;
                if (!StringUtils.isBlank(dataId)) {
                    where += " dataId not like '" + generateLikeArgument(dataId) + "'";
                    isFirstSub = false;
                }
                if (!StringUtils.isBlank(group)) {
                    if (!isFirstSub) {
                        where += " or ";
                    }
                    where += " groupId not like '" + generateLikeArgument(group) + "'";
                    isFirstSub = false;
                }
                if (!StringUtils.isBlank(appName)) {
                    if (!isFirstSub) {
                        where += " or ";
                    }
                    where += " app_name != '" + appName + "'";
                    isFirstSub = false;
                }
                where += ") ";
            } else {
                if (isFirst) {
                    isFirst = false;
                    where += " and ";
                } else {
                    where += " or ";
                }
                where += "(";
                boolean isFirstSub = true;
                if (!StringUtils.isBlank(dataId)) {
                    where += " dataId like '" + generateLikeArgument(dataId) + "'";
                    isFirstSub = false;
                }
                if (!StringUtils.isBlank(group)) {
                    if (!isFirstSub) {
                        where += " and ";
                    }
                    where += " group_id like '" + generateLikeArgument(group) + "'";
                    isFirstSub = false;
                }
                if (!StringUtils.isBlank(appName)) {
                    if (!isFirstSub) {
                        where += " and ";
                    }
                    where += " appName = '" + appName + "'";
                    isFirstSub = false;
                }
                where += ") ";
            }
        }

        TypedQuery<ConfigInfoEntity> query
            = entityManagerFactory.createEntityManager().createQuery(
            jpql + where, ConfigInfoEntity.class);
        List<ConfigInfoEntity> dataList =
            query.setFirstResult((pageNo - 1) * pageSize).setMaxResults(pageSize).getResultList();

        TypedQuery<Integer> countQuery
            = entityManagerFactory.createEntityManager().createQuery(
            "select count(*)" + jpql + where, Integer.class);
        Integer count = countQuery.getSingleResult();
        List<ConfigInfo> list = dataList.stream().map(this::convertConfigInfo).collect(Collectors.toList());

        Page<ConfigInfo> page = new Page<>();
        page.setPagesAvailable((count - 1) / pageSize + 1);
        page.setTotalCount(count);
        page.setPageNumber(pageNo);
        page.setPageItems(list);
        return page;
    }

    @Override
    public Page<ConfigInfo> findConfigInfoLike(int pageNo, int pageSize, String dataId, String group, String tenant,
                                               String appName, String content) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        if (StringUtils.isBlank(dataId) && StringUtils.isBlank(group)) {
            if (StringUtils.isBlank(appName)) {
                return this.findAllConfigInfo(pageNo, pageSize, tenantTmp);
            } else {
                return this.findConfigInfoByApp(pageNo, pageSize, tenantTmp, appName);
            }
        }

        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

        Specification<ConfigInfoEntity> specification = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(criteriaBuilder.like(root.get("tenantId"), generateLikeArgument(tenantTmp)));
            if (!StringUtils.isBlank(dataId)) {
                predicates.add(criteriaBuilder.like(root.get("dataId"), generateLikeArgument(dataId)));
            }
            if (!StringUtils.isBlank(dataId)) {
                predicates.add(criteriaBuilder.like(root.get("groupId"), generateLikeArgument(group)));
            }
            if (!StringUtils.isBlank(appName)) {
                predicates.add(criteriaBuilder.equal(root.get("appName"), appName));
            }
            if (!StringUtils.isBlank(content)) {
                predicates.add(criteriaBuilder.like(root.get("content"), generateLikeArgument(content)));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
        };

        org.springframework.data.domain.Page<ConfigInfoEntity> datas = configInfoRepository.findAll(specification,
            pageRequest);
        return convertConfigInfoList(pageNo, datas);
    }

    @Override
    public Page<ConfigInfo> findConfigInfoLike4Page(int pageNo, int pageSize, String dataId, String group,
                                                    String tenant, Map<String, Object> configAdvanceInfo) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName");
        final String content = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("content");
        final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");

        if (StringUtils.isBlank(configTags)) {
            Specification<ConfigInfoEntity> specification = (root, criteriaQuery, criteriaBuilder) -> {
                List<Predicate> predicates = new ArrayList<>();
                predicates.add(criteriaBuilder.like(root.get("tenantId"), generateLikeArgument(tenantTmp)));
                if (!StringUtils.isBlank(dataId)) {
                    predicates.add(criteriaBuilder.like(root.get("dataId"), generateLikeArgument(dataId)));
                }
                if (!StringUtils.isBlank(group)) {
                    predicates.add(criteriaBuilder.like(root.get("groupId"), generateLikeArgument(group)));
                }
                if (!StringUtils.isBlank(appName)) {
                    predicates.add(criteriaBuilder.equal(root.get("groupId"), appName));
                }
                if (!StringUtils.isBlank(content)) {
                    predicates.add(criteriaBuilder.like(root.get("groupId"), generateLikeArgument(content)));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
            };
            PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);

            org.springframework.data.domain.Page<ConfigInfoEntity> datas
                = configInfoRepository.findAll(specification,
                pageRequest);

            return convertConfigInfoList(pageNo, datas);

        }


        String jpql = "from ConfigInfoEntity ci left join ConfigTagsRelationEntity ctr on (ctr.id = ci.id) ";
        String where = "where ci.tenantId like '" + generateLikeArgument(tenantTmp) + "'";
        if (!StringUtils.isBlank(dataId)) {
            where += " and ci.dataId like '" + generateLikeArgument(dataId) + "'";
        }
        if (!StringUtils.isBlank(group)) {
            where += " and ci.groupId like '" + generateLikeArgument(group) + "'";
        }
        if (!StringUtils.isBlank(appName)) {
            where += " and ci.appName = '" + appName + "'";
        }
        if (!StringUtils.isBlank(content)) {
            where += " and ci.content like '" + generateLikeArgument(content) + "'";
        }

        where += " and ctr.tagName in ";
        String[] tagArr = configTags.split(",");
        where += Arrays.asList(tagArr).toString()
            .replace("[", "('")
            .replace("]", "')")
            .replace(", ", "', '");

        EntityManager entityManager = entityManagerFactory.createEntityManager();
        TypedQuery<ConfigInfoEntity> query = entityManager.createQuery(jpql + where, ConfigInfoEntity.class);
        TypedQuery<Integer> countQuery = entityManager.createQuery("select count(*) " + jpql + where, Integer.class);
        List<ConfigInfoEntity> dataList =
            query.setFirstResult((pageNo - 1) * pageSize).setMaxResults(pageSize).getResultList();
        Integer count = countQuery.getSingleResult();

        List<ConfigInfo> list = dataList.stream().map(this::convertConfigInfo).collect(Collectors.toList());
        Page<ConfigInfo> page = new Page<>();
        page.setPageNumber(pageNo);
        page.setPagesAvailable((count - 1) / pageSize + 1);
        page.setTotalCount(count);
        page.setPageItems(list);

        return page;

    }

    @Override
    public List<ConfigInfo> findConfigInfosByIds(String ids) {
        if (StringUtils.isBlank(ids)) {
            return null;
        }
        List<Long> idList = Arrays.stream(ids.split(",")).map(Long::parseLong).collect(Collectors.toList());
        List<ConfigInfoEntity> dataList = configInfoRepository.findAllById(idList);
        return dataList.stream().map(this::convertConfigInfo).collect(Collectors.toList());
    }

    @Override
    public long findConfigMaxId() {
        Long maxId = configInfoRepository.findMaxId();
        return maxId == null ? 0 : maxId;
    }

    @Override
    public List<String> findDatumIdByContent(String dataId, String groupId, String content) {
        List<ConfigInfoAggrEntity> datas =
            configInfoAggrRepository.findByDataIdAndGroupIdAndTenantId(dataId, groupId, content);
        return datas.stream().map(ConfigInfoAggrEntity::getDatumId).collect(Collectors.toList());
    }

    @Override
    public List<ConfigInfo> findDeletedConfig(Timestamp startTime, Timestamp endTime) {
        List<HisConfigInfoEntity> dataList =
            hisConfigInfoRepository.findByGmtModifiedBetween(startTime.toLocalDateTime(), endTime.toLocalDateTime());

        return dataList.stream().map(data -> {
            String dataId = data.getDataId();
            String group = data.getGroupId();
            String tenant = data.getTenantId();
            ConfigInfo config = new ConfigInfo();
            config.setDataId(dataId);
            config.setGroup(group);
            config.setTenant(tenant);
            return config;
        }).collect(Collectors.toList());
    }


    @Override
    public ConfigInfoAggr findSingleConfigInfoAggr(String dataId, String group, String tenant, String datumId) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        ConfigInfoAggrEntity data =
            configInfoAggrRepository.findByDataIdAndGroupIdAndTenantIdAndDatumId(dataId, group, tenantTmp, datumId);
        if (data == null) {
            return null;
        }
        ConfigInfoAggr info = new ConfigInfoAggr();
        info.setDataId(data.getDataId());
        info.setGroup(data.getGroupId());
        info.setDatumId(data.getDatumId());
        info.setTenant(data.getTenantId());
        info.setAppName(data.getAppName());
        info.setContent(data.getContent());
        return info;

    }

    @Override
    public List<TenantInfo> findTenantByKp(String kp) {
        List<TenantInfoEntity> dataList = tenantInfoRepository.findAllByKp(kp);
        return dataList.stream().map(this::convertTenantInfo).collect(Collectors.toList());
    }

    private TenantInfo convertTenantInfo(TenantInfoEntity data) {
        if (data == null) {
            return null;
        }
        TenantInfo info = new TenantInfo();
        info.setTenantId(data.getTenantId());
        info.setTenantName(data.getTenantName());
        info.setTenantDesc(data.getTenantDesc());
        return info;
    }

    @Override
    public TenantInfo findTenantByKp(String kp, String tenantId) {
        TenantInfoEntity data = tenantInfoRepository.findByKpAndTenantId(kp, tenantId);
        return convertTenantInfo(data);
    }

    @Override
    public List<String> getConfigTagsByTenant(String tenant) {
        List<ConfigTagsRelationEntity> datas = configTagsRelationRepository.findByTenantId(tenant);
        return datas.stream().map(ConfigTagsRelationEntity::getTagName).collect(Collectors.toList());
    }

    @Override
    public List<String> getGroupIdList(int page, int pageSize) {
        PageRequest pageRequest = PageRequest.of((page - 1), pageSize);
        return configInfoRepository.findGroupIds(pageRequest);
    }

    @Override
    public List<String> getTenantIdList(int page, int pageSize) {
        PageRequest pageRequest = PageRequest.of((page - 1), pageSize);
        return configInfoRepository.findTenantIds(pageRequest);
    }

    @Override
    public void insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time, Map<String,
        Object> configAdvanceInfo) {
        insertOrUpdate(srcIp, srcUser, configInfo, time, configAdvanceInfo, true);
    }

    @Autowired
    protected TransactionTemplate transactionTemplate;

    private <T> T execute(Function<TransactionStatus, T> function) {
        return transactionTemplate.execute(status -> {
            T result = null;
            try {
                result = function.apply(status);
            } catch (Exception e) {
                defaultLog.error(e.getMessage(), e);
                status.setRollbackOnly();
                throw e;
            }
            return result;
        });
    }

    @Override
    public void insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time, Map<String,
        Object> configAdvanceInfo, boolean notify) {
        try {
            execute((status) -> {
                addConfigInfo(srcIp, srcUser, configInfo, time, configAdvanceInfo, notify);
                return true;
            });

        } catch (DataIntegrityViolationException ive) { // 唯一性约束冲突
            updateConfigInfo(configInfo, srcIp, srcUser, time, configAdvanceInfo, notify);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void insertOrUpdateBeta(ConfigInfo configInfo, String betaIps, String srcIp, String srcUser,
                                   Timestamp time, boolean notify) {

        try {
            execute((status) -> {
                addConfigInfo4Beta(configInfo, betaIps, srcIp, null, time, notify);
                return true;
            });

        } catch (DataIntegrityViolationException ive) { // 唯一性约束冲突
            updateConfigInfo4Beta(configInfo, srcIp, null, time, notify);
        }

    }

    @Override
    public void insertOrUpdateSub(SubInfo subInfo) {
        try {
            execute((status) -> {
                addConfigSubAtomic(subInfo.getDataId(), subInfo.getGroup(), subInfo.getAppName(), subInfo.getDate());
                return true;
            });

        } catch (DataIntegrityViolationException ive) { // 唯一性约束冲突
            updateConfigSubAtomic(subInfo.getDataId(), subInfo.getGroup(), subInfo.getAppName(), subInfo.getDate());
        }
    }

    @Autowired
    private AppConfigdataRelationSubsRepository appConfigdataRelationSubsRepository;

    private void updateConfigSubAtomic(final String dataId, final String group, final String appName,
                                       final Timestamp time) {
        final String appNameTmp = appName == null ? "" : appName;
        appConfigdataRelationSubsRepository.updateData(dataId,group, appNameTmp, DateUtils.convert(time));
    }


    private void addConfigSubAtomic(final String dataId, final String group, final String appName,
                                    final Timestamp date) {
        final String appNameTmp = appName == null ? "" : appName;
        AppConfigdataRelationSubsEntity data = new AppConfigdataRelationSubsEntity();
        data.setDataId(dataId);
        data.setAppName(appNameTmp);
        data.setGroupId(group);
        data.setGmtModified(DateUtils.convert(date));
        appConfigdataRelationSubsRepository.save(data);
    }


    @Override
    public void insertOrUpdateTag(ConfigInfo configInfo, String tag, String srcIp, String srcUser, Timestamp time,
                                  boolean notify) {
        try {
            execute((status) -> {
                addConfigInfo4Tag(configInfo, tag, srcIp, null, time, notify);
                return true;
            });

        } catch (DataIntegrityViolationException ive) { // 唯一性约束冲突
            updateConfigInfo4Tag(configInfo, tag, srcIp, null, time, notify);
        }
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void insertTenantInfoAtomic(String kp, String tenantId, String tenantName, String tenantDesc,
                                       String createResoure, long time) {
        TenantInfoEntity data = new TenantInfoEntity();
        data.setKp(kp)
            .setTenantId(tenantId)
            .setTenantName(tenantName)
            .setTenantDesc(tenantDesc)
            .setCreateSource(createResoure)
            .setGmtCreate(time)
            .setGmtModified(time);
        tenantInfoRepository.save(data);
    }

    @Override
    public boolean isExistTable(String tableName) {
        try {
            String sql = "SELECT COUNT(*) FROM " + tableName.toLowerCase();
            entityManagerFactory.createEntityManager().createNativeQuery(sql).getFirstResult();
            return true;
        } catch (Throwable e) {
            try {
                String sql = "SELECT COUNT(*) FROM " + tableName.toUpperCase();
                entityManagerFactory.createEntityManager().createNativeQuery(sql).getFirstResult();
                return true;
            } catch (Throwable e1) {
                return false;
            }
        }
    }

    @Override
    public List<PersistService.ConfigInfoWrapper> listAllGroupKeyMd5() {
        final int pageSize = 10000;
        int totalCount = configInfoCount();
        int pageCount = (int) Math.ceil(totalCount * 1.0 / pageSize);
        List<PersistService.ConfigInfoWrapper> allConfigInfo = new ArrayList<>();
        for (int pageNo = 1; pageNo <= pageCount; pageNo++) {
            List<PersistService.ConfigInfoWrapper> configInfoList = listGroupKeyMd5ByPage(pageNo, pageSize);
            allConfigInfo.addAll(configInfoList);
        }
        return allConfigInfo;
    }


    private List<PersistService.ConfigInfoWrapper> listGroupKeyMd5ByPage(int pageNo, int pageSize) {
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize);
        org.springframework.data.domain.Page<ConfigInfoEntity> datas = configInfoRepository.findAll(pageRequest);

        return datas.stream().map(data -> {
            PersistService.ConfigInfoWrapper info = new PersistService.ConfigInfoWrapper();

            info.setDataId(data.getDataId());
            info.setGroup(data.getGroupId());
            info.setTenant(data.getTenantId());
            info.setAppName(data.getAppName());
            info.setType(data.getType());
            info.setContent(data.getContent());
            info.setId(data.getId());
            info.setLastModified(DateUtils.convertMilli(data.getGmtModified()));
            info.setMd5(data.getMd5());
            return info;
        }).collect(Collectors.toList());
    }

    @Override
    public PersistService.ConfigInfoWrapper queryConfigInfo(String dataId, String group, String tenant) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        ConfigInfoEntity data =
            configInfoRepository.findByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);
        if (data == null) {
            return null;
        }
        PersistService.ConfigInfoWrapper info = new PersistService.ConfigInfoWrapper();

        info.setDataId(data.getDataId());
        info.setGroup(data.getGroupId());
        info.setTenant(data.getTenantId());
        info.setAppName(data.getAppName());
        info.setType(data.getType());
        info.setContent(data.getContent());
        info.setId(data.getId());

        info.setLastModified(DateUtils.convertMilli(data.getGmtModified()));

        info.setMd5(data.getMd5());
        return info;

    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void removeAggrConfigInfo(String dataId, String group, String tenant) {
        final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        configInfoAggrRepository.deleteByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void removeConfigHistory(Timestamp startTime, int limitSize) {
        Pageable pageable = PageRequest.of(0, limitSize);
        org.springframework.data.domain.Page<HisConfigInfoEntity> page = hisConfigInfoRepository
            .findByGmtModifiedBefore(startTime.toLocalDateTime(), pageable);
        List<Long> ids = page.getContent().stream().map(HisConfigInfoEntity::getId).collect(Collectors.toList());
        hisConfigInfoRepository.deleteByIdIn(ids);
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void removeConfigInfo(String dataId, String group, String tenant, String srcIp, String srcUser) {

        final Timestamp time = new Timestamp(System.currentTimeMillis());

        ConfigInfo configInfo = findConfigInfo(dataId, group, tenant);
        if (configInfo != null) {
            removeConfigInfoAtomic(dataId, group, tenant, srcIp, srcUser);
            removeTagByIdAtomic(configInfo.getId());
            insertConfigHistoryAtomic(configInfo.getId(), configInfo, srcIp, srcUser, time, "D");
        }
    }

    private void removeConfigInfoAtomic(final String dataId, final String group, final String tenant,
                                        final String srcIp,
                                        final String srcUser) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        configInfoRepository.deleteByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void removeConfigInfo4Beta(String dataId, String group, String tenant) {
        final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;

        ConfigInfo configInfo = findConfigInfo4Beta(dataId, group, tenant);
        if (configInfo != null) {
            configInfoBetaRepository.deleteByDataIdAndGroupIdAndTenantId(dataId, group, tenantTmp);
        }
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public List<ConfigInfo> removeConfigInfoByIds(List<Long> ids, String srcIp, String srcUser) {
        if (CollectionUtils.isEmpty(ids)) {
            return null;
        }
        ids.removeAll(Collections.singleton(null));
        final Timestamp time = new Timestamp(System.currentTimeMillis());

        String idsStr = Joiner.on(",").join(ids);
        List<ConfigInfo> configInfoList = findConfigInfosByIds(idsStr);
        if (!CollectionUtils.isEmpty(configInfoList)) {
            removeConfigInfoByIdsAtomic(idsStr);
            for (ConfigInfo configInfo : configInfoList) {
                removeTagByIdAtomic(configInfo.getId());
                insertConfigHistoryAtomic(configInfo.getId(), configInfo, srcIp, srcUser, time, "D");
            }
        }
        return configInfoList;
    }

    private void removeConfigInfoByIdsAtomic(final String ids) {
        if (StringUtils.isBlank(ids)) {
            return;
        }
        List<Long> list = Arrays.stream(ids.split(",")).map(Long::parseLong).collect(Collectors.toList());
        configInfoRepository.deleteByIdIn(list);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void removeConfigInfoTag(String dataId, String group, String tenant, String tag, String srcIp,
                                    String srcUser) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        String tagTmp = StringUtils.isBlank(tag) ? StringUtils.EMPTY : tag;
        configInfoTagRepository.deleteByDataIdAndGroupIdAndTenantIdAndTagId(dataId, group, tenantTmp, tagTmp);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void removeSingleAggrConfigInfo(String dataId, String group, String tenant, String datumId) {
        final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        configInfoAggrRepository.deleteByDataIdAndGroupIdAndTenantIdAndDatumId(dataId, group, tenantTmp, datumId);
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void removeTagByIdAtomic(long id) {
        Optional<ConfigTagsRelationEntity> optional = configTagsRelationRepository.findById(id);
        if (optional.isPresent()){
            configTagsRelationRepository.deleteById(id);
        }

    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void removeTenantInfoAtomic(String kp, String tenantId) {
        TenantInfoEntity data = tenantInfoRepository.findByKpAndTenantId(kp, tenantId);
        if (data != null){
            tenantInfoRepository.deleteByKpAndTenantId(kp, tenantId);
        }
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public boolean replaceAggr(String dataId, String group, String tenant, Map<String, String> datumMap,
                               String appName) {
        try {
            String appNameTmp = appName == null ? "" : appName;
            removeAggrConfigInfo(dataId, group, tenant);
            String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
            for (Map.Entry<String, String> datumEntry : datumMap.entrySet()) {
                ConfigInfoAggrEntity entity = buildConfigInfoAggrEntity(dataId, group, tenantTmp,
                    datumEntry.getKey(), appNameTmp, datumEntry.getValue());
                configInfoAggrRepository.save(entity);
            }
            return Boolean.TRUE;
        } catch (Exception e) {
            fatalLog.error("[db-error] " + e.toString(), e);
            return Boolean.FALSE;
        }

    }

    private ConfigInfoAggrEntity buildConfigInfoAggrEntity(String dataId, String groupId,
                                                           String tenantId, String datumId, String appName,
                                                           String content) {
        ConfigInfoAggrEntity entity = new ConfigInfoAggrEntity();
        return entity.setDataId(dataId)
            .setGroupId(groupId)
            .setTenantId(tenantId)
            .setDatumId(datumId)
            .setAppName(appName)
            .setContent(content)
            .setGmtModified(LocalDateTime.now());
    }

    @Override
    public List<String> selectTagByConfig(String dataId, String group, String tenant) {

        List<ConfigTagsRelationEntity> configTagsRelationEntities = configTagsRelationRepository
            .findAllByDataIdAndGroupIdAndTenantId(dataId, group, tenant);
        return configTagsRelationEntities.stream()
            .map(ConfigTagsRelationEntity::getTagName).collect(Collectors.toList());
    }

    @Autowired
    private TenantInfoRepository tenantInfoRepository;

    @Override
    public int tenantInfoCountByTenantId(String tenantId) {
        Assert.hasText(tenantId, "tenantId can not be null");
        return tenantInfoRepository.countByTenantId(tenantId);
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void updateConfigInfo(ConfigInfo configInfo, String srcIp, String srcUser, Timestamp time, Map<String,
        Object> configAdvanceInfo, boolean notify) {
        ConfigInfo oldConfigInfo = findConfigInfo(configInfo.getDataId(), configInfo.getGroup(),
            configInfo.getTenant());
        String appNameTmp = oldConfigInfo.getAppName();
        // 用户传过来的appName不为空，则用持久化用户的appName，否则用db的;清空appName的时候需要传空串
        if (configInfo.getAppName() == null) {
            configInfo.setAppName(appNameTmp);
        }
        updateConfigInfoAtomic(configInfo, srcIp, srcUser, time, configAdvanceInfo);
        String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");
        if (configTags != null) {
            // 删除所有tag，然后再重新创建
            removeTagByIdAtomic(oldConfigInfo.getId());
            addConfiTagsRelationAtomic(oldConfigInfo.getId(), configTags, configInfo.getDataId(),
                configInfo.getGroup(), configInfo.getTenant());
        }
        insertConfigHistoryAtomic(oldConfigInfo.getId(), oldConfigInfo, srcIp, srcUser, time, "U");
        if (notify) {
            EventDispatcher.fireEvent(new ConfigDataChangeEvent(false, configInfo.getDataId(),
                configInfo.getGroup(), configInfo.getTenant(), time.getTime()));
        }
    }

    private void updateConfigInfoAtomic(final ConfigInfo configInfo, final String srcIp, final String srcUser,
                                        final Timestamp time, Map<String, Object> configAdvanceInfo) {
        String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName();
        String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant();
        final String md5Tmp = MD5.getInstance().getMD5String(configInfo.getContent());
        String desc = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("desc","");
        String use = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("use","");
        String effect = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("effect","");
        String type = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("type","");
        String schema = configAdvanceInfo == null ? null : (String) configAdvanceInfo.getOrDefault("schema","");

        ConfigInfoEntity configInfoEntity = configInfoRepository.findByDataIdAndGroupIdAndTenantId(
            configInfo.getDataId(), configInfo.getGroup(), tenantTmp);
        configInfoEntity.setContent(configInfo.getContent())
            .setMd5(md5Tmp)
            .setSrcIp(srcIp)
            .setSrcUser(srcUser)
            .setGmtModified(time.toLocalDateTime())
            .setGmtCreate(time.toLocalDateTime())
            .setAppName(appNameTmp)
            .setCDesc(desc)
            .setCUse(use)
            .setEffect(effect)
            .setType(type)
            .setCSchema(schema);
        configInfoRepository.save(configInfoEntity);
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void updateConfigInfo4Beta(ConfigInfo configInfo, String srcIp, String srcUser, Timestamp time,
                                      boolean notify) {
        String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName();
        String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant();

        String md5 = MD5.getInstance().getMD5String(configInfo.getContent());

        ConfigInfoBetaEntity configInfoBetaEntity = configInfoBetaRepository.findByDataIdAndGroupIdAndTenantId(
            configInfo.getDataId(), configInfo.getGroup(), tenantTmp);
        configInfoBetaEntity.setContent(configInfo.getContent())
            .setMd5(md5)
            .setSrcIp(srcIp)
            .setSrcUser(srcUser)
            .setGmtModified(time.toLocalDateTime())
            .setGmtCreate(time.toLocalDateTime())
            .setAppName(appNameTmp);
        configInfoBetaRepository.save(configInfoBetaEntity);
        if (notify) {
            EventDispatcher.fireEvent(new ConfigDataChangeEvent(true, configInfo.getDataId(), configInfo.getGroup(),
                tenantTmp, time.getTime()));
        }
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void updateConfigInfo4Tag(ConfigInfo configInfo, String tag, String srcIp, String srcUser, Timestamp time,
                                     boolean notify) {
        String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName();
        String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant();
        String tagTmp = StringUtils.isBlank(tag) ? StringUtils.EMPTY : tag.trim();

        String md5 = MD5.getInstance().getMD5String(configInfo.getContent());
        ConfigInfoTagEntity configInfoTagEntity = configInfoTagRepository.findByDataIdAndGroupIdAndTenantIdAndTagId(
            configInfo.getDataId(), configInfo.getGroup(), tenantTmp, tagTmp);
        configInfoTagEntity.setContent(configInfo.getContent())
            .setMd5(md5)
            .setSrcIp(srcIp)
            .setSrcUser(srcUser)
            .setGmtModified(time.toLocalDateTime())
            .setAppName(appNameTmp);
        configInfoTagRepository.save(configInfoTagEntity);
        if (notify) {
            EventDispatcher.fireEvent(new ConfigDataChangeEvent(true, configInfo.getDataId(), configInfo.getGroup(),
                tenantTmp, tagTmp, time.getTime()));
        }
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void updateMd5(String dataId, String group, String tenant, String md5, Timestamp lastTime) {
        String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        ConfigInfoEntity configInfoEntity = configInfoRepository.findByDataIdAndGroupIdAndTenantIdAndGmtModified(
            dataId, group, tenantTmp, lastTime.toLocalDateTime());
        configInfoEntity.setMd5(md5);
        configInfoRepository.save(configInfoEntity);
    }

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void updateTenantNameAtomic(String kp, String tenantId, String tenantName, String tenantDesc) {
        TenantInfoEntity tenantInfoEntity = tenantInfoRepository.findByTenantIdAndKp(tenantId, kp);
        tenantInfoEntity.setTenantName(tenantName)
            .setTenantDesc(tenantDesc)
            .setGmtModified(System.currentTimeMillis());
        tenantInfoRepository.save(tenantInfoEntity);
    }
}
